#-*- coding: utf-8 -*-
import random
from helpers import binaryRandom

class Mutation():

    def __call__(self, population, theta):    
        for individual in population:
            for gene in individual:
                if binaryRandom(theta):
                    gene = (gene + 1) % 2
        return population



class UniformCrossover():
    def __call__(self, population):
	    random.shuffle(population)
	    i = 0	
	    newPopulation = []
	    while i+1 < len(population):	
		    x1 = population[i][0]	
		    x2 = population[i+1][0]
		    y1 = []
		    y2 = []
		    for k in range(len(x1)):
			    if binaryRandom(0.5):
				    y1.append(x1[k])
				    y2.append(x2[k])
			    else:
				    y1.append(x2[k])
				    y2.append(x1[k])	
		    newPopulation += [y1, y2]
		    i += 2
	    return newPopulation


class OnepointCrossover():
    
	def __call__(self, population):

		random.shuffle(population)
		newPopulation = []
		i = 0
		while (i+1) < len(population):
			x1 = population[i][0]
			x2 = population[i+1][0]
            
			k = random.randint(0, len(x1)-1)
            
			y1 = x1[:k] + x2[k:]
			y2 = x2[:k] + x1[k:]
            
			newPopulation += [y1, y2]

			i += 2
		return newPopulation

class BlockSelection():

	def __call__(self, population, size):
		if size <= len(population):
			sortedPopulation = sorted(population, key = lambda el: el[1])
			sortedPopulation[:size] = sortedPopulation[len(population) - size:]
			return sortedPopulation
		return population

class BestBlockSelection():

	def __call__(self, population, size):
		if size <= len(population):
			sortedPopulation = sorted(population, key = lambda el: el[1])
			sortedPopulation[:size] = sortedPopulation[len(population) - size:]
			return sortedPopulation
		return population


class RouletteBlockSelection():

	def __init__(self, function):
		self.function = function

	def __call__(self, population, size):
		if size <= len(population):
			population = self.function(population)
			population = sorted(population, key = lambda el: el[1], reverse = True)
            
			count = 0
			replacement = []
			for individual in population:
				if count < size:
					if binaryRandom(individual[1]):
						replacement.append(individual)
						count += 1

			population[len(population) - count:] = replacement
		return population



def fitnessFunction(population):
	"""
		Funkcja przystosowania.
		Przyjmuje ocenioną populację, zwraca dodatkowo informacje o wartościach funkcji przystosowania.
	"""
	#minimum po wartościach funkcji przystosowania
	fitnesses = [individual[1] for individual in population]
	minFitness = min(fitnesses)
	fitnesses = [f - minFitness for f in fitnesses]
	sumFitnesses = sum(fitnesses)
    
	for p in population:
		fitVal = (p[1] - minFitness) / sumFitnesses
		p.append(fitVal)

	return population




def linearRank(population):
	"""
		Ranking liniowy.
		Przyjmuje ocenioną populację. Zwraca prawdopodobieństwa przypisane metodą rankingu.
	"""
	population = sorted(population, key = lambda el: el[1])
	mi = len(population)
    
	for k in range(mi):
		p = (2 * k) / (mi * (mi - 1))
		population[k].append(p)

	return population




def simpleScaledFitnessFunction(population, worst):
	"""
		Simple scaling dla funkcji przystosowania.
		Oprócz uwzględniania wartości najgorszej, przeskalowuje wartości na prawdopodobieństwa.
	"""
	fitPopulation = fitnessFunction(population)
	sumFitnesses = sum([f[2] for f in fitPopulation])

	for f in fitPopulation:
		f[2] = (f[2] - worst) / sumFitnesses
